package server;

import java.io.*;
import java.net.*;
import java.util.*;

import protocol.*;
import protocol.Error;
import spel.Spel;
import spel.Speler;

/**
 * Server voor een spelletje PentagoXL
 * Luistert op een bepaalde poort naar clients.
 * Is in staat om voor meerde clients een spel te spelen
 * 
 * @author Tim en Stef
 */
public class Server extends Observable implements Runnable, Observer {
	private int port;
	private Collection<ClientHandler> threads;
	private Map<String, ClientHandler> spelers;
	private List<String> req2, req3, req4;
	private static String[] FORBIDDEN = new String[] { "leon", "bas", "fuck",
			"kut", "bitch" };

	
	/** Construeert een nieuw Server-object. */
	public Server(int port) {
		this.port = port;
		threads = new ArrayList<ClientHandler>();
		spelers = new HashMap<String, ClientHandler>();

		req2 = new ArrayList<String>();
		req3 = new ArrayList<String>();
		req4 = new ArrayList<String>();
	}

	/**
	 * Luistert op de port van deze Server of er Clients zijn die een verbinding
	 * willen maken. Voor elke nieuwe socketverbinding wordt een nieuw
	 * ClientHandler thread opgestart die de verdere communicatie met de Client
	 * afhandelt.
	 */
	public void run() {

		try {
			ServerSocket ss = new ServerSocket(port);
			this.setChanged();
			this.notifyObservers("Connection open");
			while (!ss.isClosed()) {
				this.addHandler(new ClientHandler(ss.accept()));
			}
		} catch (IOException e) {
			this.setChanged();
			this.notifyObservers("Port not available");
		}

	}

	/**
	 * Stuurt een bericht via de collectie van aangesloten ClientHandlers naar
	 * alle aangesloten Clients.
	 * 
	 * @param msg
	 *            bericht dat verstuurd wordt
	 */
	public void broadcastCommand(String command, String[] args) {
		this.setChanged();
		synchronized (spelers) {
			for (ClientHandler s : spelers.values()) {
				s.sendCommand(command, args);
			}
		}
	}

	/**
	 * Voegt een ClientHandler aan de collectie van ClientHandlers toe.
	 * 
	 * @param handler
	 *            ClientHandler die wordt toegevoegd
	 */
	public void addHandler(ClientHandler handler) {
		handler.addObserver(this);
		threads.add(handler);
		new Thread(handler).start();
	}

	/**
	 * Verwijdert een ClientHandler uit de collectie van ClientHandlers.
	 * 
	 * @param handler
	 *            ClientHandler die verwijderd wordt
	 */
	public void removeHandler(ClientHandler handler) {
		threads.remove(handler);
		synchronized (spelers) {
			if (spelers.containsValue(handler))
				addMessage("Player left: " + handler.getName());
			spelers.remove(handler.getName());
		}

	}

	private void processCommand(String command, ClientHandler c) {
		String[] tokens = command.split("" + ServerProtocol.DELIM);
		if (tokens[0].equals(ClientProtocol.JOIN)) {
			if (tokens.length == 2) {
				if (spelers.containsKey(tokens[1])) {
					c.sendCommand(ServerProtocol.ERROR, new String[] {
							"" + protocol.Error.NAME_TAKEN.value(),
							"Name already taken" });
				} else if (!notForbidden(tokens[1])) {
					c.sendCommand(ServerProtocol.ERROR, new String[] {
							"" + protocol.Error.INVALID_NAME.value(),
							"Illegal player name" });
				} else {

					synchronized (spelers) {
						addMessage("Player joined: " + tokens[1]);
						spelers.put(tokens[1], c);
					}
					c.setName(tokens[1]);
					c.sendCommand(ServerProtocol.ACCEPT, null);
					this.broadcastCommand(ServerProtocol.CHAT,
					new String[] { "Player joined: " + tokens[1] });
				}
				

			}

		} else if (tokens[0].equals(ClientProtocol.REQUEST)) {
			if (tokens.length == 2) {
				int count;
				try {
					count = Integer.parseInt(tokens[1]);
					List<String> stack = null;
					switch (count) {
					case 2:
						stack = req2;
						break;
					case 3:
						stack = req3;
						break;
					case 4:
						stack = req4;
						break;
					}
					if (stack != null && spelers.containsKey(c.getName())) {
						stack.add(c.getName());
						if (stack.size() >= count) {
							String[] names = new String[count];
							List<Speler> slist = new ArrayList(count);
							for (int i = 0; i < count; i++) {
								names[i] = stack.get(i);
							}

							for (String s : names) {
								stack.remove(s);
								spelers.get(s).sendCommand(
										ServerProtocol.START_GAME, names);
								slist.add(new NetwerkSpeler(spelers.get(s)));
								String message = "Starting game with: ";
								for (String name : names) {
									message = message + name + ", ";
								}
								this.addMessage(message);
							}
							Spel spel = new Spel(slist);
							for (Speler s : slist) {
								if (s instanceof NetwerkSpeler) {
									spel.addObserver((NetwerkSpeler) s);
								}
							}
							new Thread(spel).start();
						} else {
							c.sendCommand(ServerProtocol.WAIT, null);
							this.addMessage(c.getName() + " added to queue "
									+ count);

						}
					}

				} catch (NumberFormatException e) {

				}

			}
		} else if (tokens[0].equals(ClientProtocol.CHAT) && tokens.length == 2) {
			this.broadcastCommand(ServerProtocol.CHAT,
					new String[] { c.getName() + ": " + tokens[1] });
		} else if (tokens[0].equals(ClientProtocol.ERROR)
				|| tokens[0].equals(ClientProtocol.DO_MOVE)) {

		} else {
			c.sendCommand(ServerProtocol.ERROR, new String[] {
					"" + Error.INVALID_COMMAND.value(),
					"Command not recognized: " + command });
		}

	}

	private boolean notForbidden(String s) {
		for (String f : FORBIDDEN) {
			if (s.toLowerCase().contains(f)) {
				return false;
			}
		}
		return true;

	}

	private void addMessage(String m) {
		this.setChanged();
		this.notifyObservers(m);
	}

	@Override
	/**
	 * handelt updates van de clienthandlers af en zal acties uitvoeren op basis van het verkregen bericht
	 */
	public void update(Observable arg0, Object arg1) {
		if (arg0 instanceof ClientHandler) {
			if (arg1 != null && arg1.equals(ClientHandler.EXIT)) {
				removeHandler((ClientHandler) arg0);
				if (spelers.containsValue(arg0))
					broadcastCommand(ServerProtocol.CHAT,
							new String[] { "Player left: "
									+ ((ClientHandler) arg0).getName() });
			} else if (arg1 != null && arg1 instanceof String) {
				processCommand((String) arg1, (ClientHandler) arg0);
			}
		}

	}

} // end of class Server
